﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

using Shared;

namespace WorldServer
{
    public class PacketHandler
    {
        #region Exceptions

        /// <summary>
        /// Thrown when parameter type mismatch is detected.
        /// </summary>
        public class ParameterTypeException : Exception { public ParameterTypeException(string Message) : base(Message) { } }
        
        /// <summary>
        /// Thrown when packet function is already registered.
        /// </summary>
        public class PacketRegisteredException : Exception { public PacketRegisteredException(string Message) : base(Message) { } }

        #endregion

        #region Fields

        /// <summary>
        /// Parameters to be passed onto the packet handling method.
        /// </summary>
        public object[] Parameters = new object[1];

        /// <summary>
        /// Contains all registered packet operations.
        /// </summary>
        private Dictionary<string, MethodInfo[]> Operations = new Dictionary<string, MethodInfo[]>();

        #endregion

        #region Construct

        /// <summary>
        /// Constructs a new packet handler.
        /// </summary>
        public PacketHandler()
        {
            this.Operations["OP0000"] = new MethodInfo[65536];
            this.Operations["OP00FF"] = new MethodInfo[65536];
            this.Operations["OPF000"] = new MethodInfo[65536];
            this.Operations["OPFFFF"] = new MethodInfo[65536];
            this.Operations["OPDEFA"] = new MethodInfo[65536];

            for (int i = 0; i < 65536; i++)
            {
                this.Operations["OP0000"][i] = null;
                this.Operations["OP00FF"][i] = null;
                this.Operations["OPF000"][i] = null;
                this.Operations["OPFFFF"][i] = null;
                this.Operations["OPDEFA"][i] = null;
            }

            foreach (Exception e in CreateMethodList())
            {
                Program.Logger.Write(LogType.Warning, "PacketHandler: {0}", e.Message);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates the method list for all registered packet functions.
        /// </summary>
        /// <returns>Returns exceptions thrown during list creation.</returns>
        private Exception[] CreateMethodList()
        {
            ParameterInfo[] ParaInfo;
            List<Exception> ExceptionList = new List<Exception>();

            foreach (MethodInfo MethInfo in typeof(Client).GetMethods())
            {
                if (!MethInfo.IsStatic && MethInfo.IsPublic)
                {
                    foreach (Attribute Attr in MethInfo.GetCustomAttributes(false))
                    {
                        if (Attr is HandlesPacket)
                        {
                            ParaInfo = MethInfo.GetParameters();

                            if ((ParaInfo.Length < 1) || (ParaInfo.Length > 1))
                            {
                                ExceptionList.Add(new TargetParameterCountException(string.Format("Too many or no parameters for function \"{0}\", expecting 1.", MethInfo.Name)));
                                break;
                            }

                            if (ParaInfo[0].ParameterType != typeof(DataPacket))
                            {
                                ExceptionList.Add(new ParameterTypeException(string.Format("The parameter has to be a DataPacket in function \"{0}\".", MethInfo.Name)));
                                break;
                            }

                            uint HPacketHeader = ((HandlesPacket)Attr).Header;

                            switch (HPacketHeader & 0xFFFF0000)
                            {
                                case 0x00000000:
                                    {
                                        if (this.Operations["OP0000"][HPacketHeader & 0xFFFF] != null)
                                        {
                                            ExceptionList.Add(new PacketRegisteredException("Packet handler has already been registered."));
                                        }
                                        else
                                        {
                                            this.Operations["OP0000"][HPacketHeader & 0xFFFF] = MethInfo;
                                        }
                                    }
                                    break;

                                case 0x00FF0000:
                                    {
                                        if (this.Operations["OP00FF"][HPacketHeader & 0xFFFF] != null)
                                        {
                                            ExceptionList.Add(new PacketRegisteredException("Packet handler has already been registered."));
                                        }
                                        else
                                        {
                                            this.Operations["OP00FF"][HPacketHeader & 0xFFFF] = MethInfo;
                                        }
                                    }
                                    break;

                                case 0xF0000000:
                                    {
                                        if (this.Operations["OPF000"][HPacketHeader & 0xFFFF] != null)
                                        {
                                            ExceptionList.Add(new PacketRegisteredException("Packet handler has already been registered."));
                                        }
                                        else
                                        {
                                            this.Operations["OPF000"][HPacketHeader & 0xFFFF] = MethInfo;
                                        }
                                    }
                                    break;

                                case 0xFFFF0000:
                                    {
                                        if (this.Operations["OPFFFF"][HPacketHeader & 0xFFFF] != null)
                                        {
                                            ExceptionList.Add(new PacketRegisteredException("Packet handler has already been registered."));
                                        }
                                        else
                                        {
                                            this.Operations["OPFFFF"][HPacketHeader & 0xFFFF] = MethInfo;
                                        }
                                    }
                                    break;

                                default:
                                    {
                                        if (this.Operations["OPDEFA"][HPacketHeader & 0xFFFF] != null)
                                        {
                                            ExceptionList.Add(new PacketRegisteredException("Packet handler has already been registered."));
                                        }
                                        else
                                        {
                                            this.Operations["OPDEFA"][HPacketHeader & 0xFFFF] = MethInfo;
                                        }
                                    }
                                    break;
                            }
                        }
                    }
                }
            }

            return ExceptionList.ToArray();
        }

        /// <summary>
        /// Invokes a registered packet function.
        /// </summary>
        /// <param name="DataClient">Client to invoke from.</param>
        /// <param name="Packet">The packet itself.</param>
        /// <param name="Header">The packet header.</param>
        /// <returns>If the packet command is valid or not.</returns>
        public bool HandleDataPacket(Client DataClient, DataPacket Packet, uint Header)
        {
            switch (Header & 0xFFFF0000)
            {
                case 0x00000000:
                    {
                        if (this.Operations["OP0000"][Header & 0xFFFF] != null)
                        {
                            this.Parameters[0] = Packet;
                            this.Operations["OP0000"][Header & 0xFFFF].Invoke(DataClient, this.Parameters);
                        }
                        else
                        {
                            return false;
                        }
                    }
                    break;

                case 0x00FF0000:
                    {
                        if (this.Operations["OP00FF"][Header & 0xFFFF] != null)
                        {
                            this.Parameters[0] = Packet;
                            this.Operations["OP00FF"][Header & 0xFFFF].Invoke(DataClient, this.Parameters);
                        }
                        else
                        {
                            return false;
                        }
                    }
                    break;

                case 0xF0000000:
                    {
                        if (this.Operations["OPF000"][Header & 0xFFFF] != null)
                        {
                            this.Parameters[0] = Packet;
                            this.Operations["OPF000"][Header & 0xFFFF].Invoke(DataClient, this.Parameters);
                        }
                        else
                        {
                            return false;
                        }
                    }
                    break;

                case 0xFFFF0000:
                    {
                        if (this.Operations["OPFFFF"][Header & 0xFFFF] != null)
                        {
                            this.Parameters[0] = Packet;
                            this.Operations["OPFFFF"][Header & 0xFFFF].Invoke(DataClient, this.Parameters);
                        }
                        else
                        {
                            return false;
                        }
                    }
                    break;

                default:
                    {
                        if (this.Operations["OPDEFA"][Header & 0xFFFF] != null)
                        {
                            this.Parameters[0] = Packet;
                            this.Operations["OPDEFA"][Header & 0xFFFF].Invoke(DataClient, this.Parameters);
                        }
                        else
                        {
                            return false;
                        }
                    }
                    break;
            }

            return true;
        }

        #endregion
    }

    /// <summary>
    /// Put this on an incoming packet to register it as a packet function.
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class HandlesPacket : Attribute
    {
        /// <summary>
        /// Packet header.
        /// </summary>
        private uint PrivateHeader;

        /// <summary>
        /// Registers a new packet command.
        /// </summary>
        /// <param name="Header">Packet header.</param>
        public HandlesPacket(uint Header)
        {
            this.PrivateHeader = Header;
        }

        /// <summary>
        /// Gets the packet header.
        /// </summary>
        public uint Header
        {
            get
            {
                return this.PrivateHeader;
            }
        }
    }
}
